/*
*  This file is part of mangusta
*
*  mangusta is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  mangusta is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with mangusta; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  US
*
*  Author: <antonino.calderone@ericsson.com>, <acaldmail@gmail.com>
*
*/


/* -------------------------------------------------------------------------- */

#include "nu_lang_defs.h"

#include "nu_functions.h"
#include "nu_token_list.h"
#include "nu_label_tbl.h"

#include "nu_stmt_array.h"
#include "nu_stmt_block.h"
#include "nu_stmt_call.h"
#include "nu_stmt_class.h"
#include "nu_stmt_end.h"
#include "nu_stmt_end_if.h"
#include "nu_stmt_end_for.h"
#include "nu_stmt_else.h"
#include "nu_stmt_exit_do.h"
#include "nu_stmt_exit_for.h"
#include "nu_stmt_exit_while.h"
#include "nu_stmt_exit_func.h"
#include "nu_stmt_for.h"
#include "nu_stmt_goto.h"
#include "nu_stmt_on_goto.h"
#include "nu_stmt_if.h"
#include "nu_stmt_decl.h"
#include "nu_stmt_print.h"
#include "nu_stmt_expr.h"
#include "nu_stmt_do.h"
#include "nu_stmt_loop_while.h"
#include "nu_stmt_while.h"
#include "nu_stmt_end_while.h"
#include "nu_stmt_func.h"
#include "nu_stmt_end_func.h"

#include "nu_parser.h"
#include "nu_expr_parser.h"
#include "nu_expr_tknzr.h"

#include "nu_stmt_geninstr.h"

#include <set>
#include <sstream>


/* -------------------------------------------------------------------------- */

namespace nu
{


/* -------------------------------------------------------------------------- */

void parser_t::remove_blank(nu::token_list_t & tl)
{
   while (!tl.empty() && (
             tl.begin()->type() == tkncl_t::BLANK ||
             tl.begin()->type() == tkncl_t::LINE_COMMENT ||
             tl.begin()->type() == tkncl_t::NEWLINE))
   {
      --tl;
   }

   while (!tl.empty() &&
          (tl.rbegin()->type() == tkncl_t::BLANK ||
           tl.begin()->type() == tkncl_t::LINE_COMMENT))
   {
      tl--;
   }
}


/* -------------------------------------------------------------------------- */

void parser_t::extract_next_token(
   token_list_t& tl,
   token_t & token,
   std::function<bool(const token_list_t& tl, const token_t& token)> check)
{
   --tl;
   remove_blank(tl);
   syntax_error_if(check(tl, token), token.expression(), token.position());
   token = *tl.begin();
}


/* -------------------------------------------------------------------------- */

void parser_t::move_sub_expression(
   token_list_t& source_tl,
   token_list_t& dst_tl,
   const std::string& id,
   tkncl_t idtype)
{
   while (!source_tl.empty() &&
          (!(source_tl.begin()->type() == idtype &&
             source_tl.begin()->identifier() == id)))
   {
      const token_t token(*source_tl.begin());
      dst_tl += token;
      --source_tl;
      remove_blank(source_tl);
   }
}


/* -------------------------------------------------------------------------- */

stmt_t::handle_t parser_t::parse_cout(
   prog_ctx_t & ctx,
   token_t token,
   nu::token_list_t & tl)
{
   --tl;
   remove_blank(tl);

   if (!tl.empty())
      token = *tl.begin();

   else
      return stmt_t::handle_t(std::make_shared<stmt_print_t>(ctx));

   syntax_error_if(
      tl.size() == 1 ||
      (token.type() != tkncl_t::OPERATOR &&
       token.identifier() != PROGLANG_STMNT_SEP),
      token.expression(),
      token.position());

   --tl;
   remove_blank(tl);

   syntax_error_if(tl.empty(), token.expression(), token.position());

   token = *tl.begin();

   if (token.type() == tkncl_t::STRING_LITERAL && tl.size() == 1)
   {
      --tl;

      //Create statement to print out a literal string
      return stmt_t::handle_t(
                std::make_shared<stmt_print_t>(ctx, token.identifier()));
   }

   return parse_arg_list<stmt_print_t, 0>(
             ctx,
             token,
             tl,
             [](const token_t&t)
   {
      return t.type() == tkncl_t::OPERATOR &&
             t.identifier() == PROGLANG_COUT_OP;
   },
   ctx);
}


/* -------------------------------------------------------------------------- */

stmt_t::handle_t parser_t::parse_expr(
   prog_ctx_t & ctx,
   token_t token,
   nu::token_list_t & tl)
{
   syntax_error_if(tl.empty(), token.expression(), token.position());

   token = *tl.begin();

   if (token.type() == tkncl_t::STRING_LITERAL && tl.size() == 1)
   {
      --tl;

      //Create statement to print out a literal string
      return stmt_t::handle_t(
                std::make_shared<stmt_expr_t>(ctx, token.identifier()));
   }

   return parse_arg_list<stmt_expr_t, 0>(
             ctx,
             token,
             tl,
             [](const token_t&t)
   {
      return t.type() == tkncl_t::OPERATOR && t.identifier() == ",";
   },
   ctx);
}


/* -------------------------------------------------------------------------- */

stmt_t::handle_t parser_t::parse_block(
   prog_ctx_t & ctx,
   nu::token_list_t & tl,
   const std::string& end_block_id,
   tkncl_t end_block_id_cl)
{
   remove_blank(tl);

   if (tl.empty())
      return stmt_empty_t::handle_t(std::make_shared<stmt_empty_t>(ctx));

   stmt_block_t::handle_t hblock(std::make_shared<stmt_block_t>(ctx));

   while (!tl.empty())
   {
      remove_blank(tl);
      (*hblock) += parse_stmt(ctx, tl);

      remove_blank(tl);

      if (tl.empty())
         break;

      token_t token(*tl.begin());

      if (!end_block_id.empty() &&
            end_block_id == token.identifier() &&
            end_block_id_cl == token.type())
      {
         --tl;
         break;
      }

      syntax_error_if(
         token.type() != tkncl_t::OPERATOR &&
         token.identifier() != PROGLANG_STMNT_SEP,
         token.expression(),
         token.position());

      --tl;
   }

   return hblock;
}


/* -------------------------------------------------------------------------- */

template<class T>
stmt_t::handle_t parser_t::parse_branch_instr(
   prog_ctx_t & ctx,
   token_t token,
   nu::token_list_t & tl)
{
   remove_blank(tl);

   syntax_error_if(
      tl.empty() ||
      token.type() != tkncl_t::IDENTIFIER,
      token.expression(),
      token.position());

   std::string label = token.identifier();

   --tl;

   return stmt_t::handle_t(std::make_shared<T>(ctx, label));
}


/* -------------------------------------------------------------------------- */

expr_any_t::handle_t parser_t::parse_sub_expr(
   prog_ctx_t & ctx,
   token_t token,
   token_list_t & tl,
   token_list_t & etl,
   bool subscription_operators)
{
   expr_any_t::handle_t sub_exp = nullptr;

   if ((subscription_operators &&
         token.type() == tkncl_t::SUBSCR_BEGIN) ||
         token.type() == tkncl_t::SUBEXP_BEGIN)
   {
      int parenthesis_level = 1;

      extract_next_token(tl, token);

      while (!tl.empty())
      {
         token = *tl.begin();
         --tl;
         remove_blank(tl);

         if (token.type() == tkncl_t::SUBEXP_BEGIN)
         {
            ++parenthesis_level;
         }

         else if (
            (subscription_operators && parenthesis_level == 1 &&
             token.type() == tkncl_t::SUBSCR_END)
            ||
            ((! subscription_operators || parenthesis_level > 1) &&
             token.type() == tkncl_t::SUBEXP_END))
         {
            --parenthesis_level;

            if (parenthesis_level < 1)
               break;
         }

         etl += token;

      }

      syntax_error_if(parenthesis_level != 0 ||
                      etl.empty(), token.expression(), token.position());
   }

   if (!etl.empty())
   {
      expr_parser_t ep;
      sub_exp = ep.compile(etl, token.position());
   }

   return sub_exp;
}


/* -------------------------------------------------------------------------- */

stmt_t::handle_t parser_t::parse_declaration(
   prog_ctx_t & ctx,
   nu::token_list_t & tl,
   bool constant,
   variable_t::type_t type)
{
   bool reference = false;

   remove_blank(tl);

   if (tl.empty())
      return stmt_t::handle_t(std::make_shared<stmt_empty_t>(ctx));

   token_t token(*tl.begin());

   if (token.type() == tkncl_t::OPERATOR &&
         token.identifier() == PROGLANG_REFERENCE_OP)
   {
      reference = true;
      --tl;
      remove_blank(tl);
      token = *tl.begin();
   }

   size_t pos = token.position();
   std::string expr = token.expression();

   syntax_error_if(token.type() != tkncl_t::IDENTIFIER, expr, pos);

   std::string identifier = token.identifier();

   syntax_error_if(identifier == PROGLANG_MAIN_FUNCTION, expr, pos);

   --tl;
   remove_blank(tl);

   // identifier() is function call
   if (tl.size()>=2 &&
         tl[0].type()==tkncl_t::SUBEXP_BEGIN &&
         tl[1].type()==tkncl_t::SUBEXP_END)
   {
      --tl;
      --tl;
      return stmt_t::handle_t(
                std::make_shared<stmt_call_t>(identifier, ctx));
   }

   syntax_error_if(tl.empty(), expr, pos);

   token = *tl.begin();

   bool subscription_operators = false;
   bool begin_subscription_detected = false;

   if ( tl.size()>1 )
   {
      begin_subscription_detected =
         token.type() == tkncl_t::SUBSCR_BEGIN;

      subscription_operators =
         begin_subscription_detected &&
         tl.rbegin()->type() == tkncl_t::SUBSCR_END;

      if ( (token.type() == tkncl_t::SUBEXP_BEGIN &&
            tl.rbegin()->type() == tkncl_t::SUBEXP_END)
            || subscription_operators )
      {
         tl--;
         --tl;
         syntax_error_if(tl.empty(), expr, pos);
         token = *tl.begin();
      }
   }


   if (
      ! subscription_operators &&
      token.type() != tkncl_t::OPERATOR &&
      token.type() != tkncl_t::SUBEXP_BEGIN &&
      token.type() != tkncl_t::SUBSCR_BEGIN)
   {
      return parse_arg_list<stmt_call_t, 0>(
                ctx,
                token,
                tl,
                [](const token_t&t)
      {
         return t.type() == tkncl_t::OPERATOR &&
                t.identifier() == ",";
      },
      identifier,
      ctx
             );
   }

   token_list_t vect_etl;

   expr_any_t::handle_t opt_vector_idx
      = parse_sub_expr(
           ctx,
           token,
           tl,
           vect_etl,
           begin_subscription_detected);

   remove_blank(tl);
   syntax_error_if(tl.empty(), expr, pos);
   token = *tl.begin();

   syntax_error_if(
      token.type() != tkncl_t::OPERATOR ||
      token.identifier() != PROGLANG_OP_ASSIGN,
      expr, pos);

   --tl;
   remove_blank(tl);

   expr_parser_t ep;
   pos = token.position();

   token_list_t etl;
   remove_blank(tl);

   move_sub_expression(
      tl,                     // source
      etl,                    // destination
      PROGLANG_STMNT_SEP,
      tkncl_t::OPERATOR  // end-of-expression
   );

   return stmt_t::handle_t(
             std::make_shared<stmt_decl_t>(
                ctx,
                identifier,
                ep.compile(etl, pos),
                opt_vector_idx,
                constant,
                reference,
                type));
}


/* -------------------------------------------------------------------------- */

stmt_t::handle_t parser_t::parse_break(
   prog_ctx_t & ctx,
   nu::token_t token,
   nu::token_list_t & tl)
{
   --tl;
   remove_blank(tl);
   syntax_error_if(
      tl.empty() && ctx.parsing_scope.empty(),
      token.expression(),
      token.position());

   std::string id;

   if (tl.empty())
   {
      id = ctx.parsing_scope.top();
   }

   else
   {
      syntax_error_if(
         tl.begin()->type() != tkncl_t::IDENTIFIER,
         token.expression(),
         token.position());

      id = tl.begin()->identifier();

      --tl;
      remove_blank(tl);
   }

   syntax_error_if(
      (id != PROGLANG_STMT_FOR &&
       id != PROGLANG_STMT_FUNCTION &&
       id != PROGLANG_STMT_WHILE &&
       id != PROGLANG_STMT_DO),
      token.expression(),
      token.position());

   if (id == PROGLANG_STMT_WHILE)
      return stmt_t::handle_t(std::make_shared<stmt_exit_while_t>(ctx));

   else if (id == PROGLANG_STMT_DO)
      return stmt_t::handle_t(std::make_shared<stmt_exit_do_t>(ctx));

   else if (id == PROGLANG_STMT_FUNCTION)
      return stmt_t::handle_t(std::make_shared<stmt_exit_func_t>(ctx));

   else if (id == PROGLANG_STMT_FOR)
      return stmt_t::handle_t(std::make_shared<stmt_exit_for_t>(ctx));

   //throws an exception
   syntax_error_if(
      true,
      token.expression(),
      token.position());

   //never executed
   return nullptr;
}


/* -------------------------------------------------------------------------- */

template<class T>
stmt_t::handle_t parser_t::parse_func(
   prog_ctx_t & ctx,
   nu::token_t token,
   nu::token_list_t & tl)
{
   std::string ret_type = "";

   if (token.identifier() == PROGLANG_STMT_FUNCTION &&
         token.type() == tkncl_t::IDENTIFIER)
   {
      remove_blank(tl);

      syntax_error_if(
         tl.empty(),
         token.expression(),
         token.position());

      token = *tl.rbegin();

      syntax_error_if(
         token.type()!= tkncl_t::IDENTIFIER ||
         token.identifier() != PROGLANG_BEGIN_BLOCK_OP,
         token.expression(),
         token.position());

      tl--;
      remove_blank(tl);

      --tl;
      remove_blank(tl);

      token = *tl.begin();

      syntax_error_if(
         tl.empty() || token.type()!= tkncl_t::IDENTIFIER,
         token.expression(),
         token.position());


      ret_type = token.identifier();
   }

   --tl;
   remove_blank(tl);
   syntax_error_if(tl.empty(), token.expression(), token.position());

   token = *tl.begin();

   syntax_error_if(
      token.type() != tkncl_t::IDENTIFIER,
      token.expression(),
      token.position());

   --tl;
   remove_blank(tl);
   syntax_error_if(tl.empty(), token.expression(), token.position());

   std::string id = token.identifier();

   bool main_function = id == PROGLANG_MAIN_FUNCTION;

   syntax_error_if(
      main_function &&
      ret_type != PROGLANG_MAIN_FUNCTION_RET_TYPE,
      token.expression(),
      token.position());


   token = *tl.begin();

   bool subscription_operator =
      token.type() == tkncl_t::SUBSCR_BEGIN;

   syntax_error_if(
      token.type() != tkncl_t::SUBEXP_BEGIN || subscription_operator,
      token.expression(),
      token.position());

   --tl;
   remove_blank(tl);

   token = *tl.begin();

   syntax_error_if(
      token.type() == tkncl_t::SUBEXP_END && subscription_operator,
      token.expression(),
      token.position());

   if (token.type() == tkncl_t::SUBEXP_END)
   {
      --tl;
      remove_blank(tl);

      syntax_error_if(!tl.empty(), token.expression(), token.position());
      return stmt_t::handle_t(std::make_shared<T>(ctx, id, ret_type));
   }

   else
   {
      //reset '(' before other tokens
      tl.data().push_front(token);
   }

   syntax_error_if(
      main_function &&
      tl.size() != 2 &&
      tl.begin()->type() != tkncl_t::SUBSCR_BEGIN &&
      (tl.begin() + 1)->type() != tkncl_t::SUBSCR_END,
      token.expression(),
      token.position());

   return parse_parameter_list<T>(
             subscription_operator ? tkncl_t::SUBSCR_BEGIN : tkncl_t::SUBEXP_BEGIN,
             subscription_operator ? tkncl_t::SUBSCR_END : tkncl_t::SUBEXP_END,
             ctx,
             token,
             tl,
             subscription_operator ? PROGLANG_END_SUBSCR_OP : PROGLANG_END_SUBEXPR_OP,
             ctx,
             id,
             ret_type);
}


/* -------------------------------------------------------------------------- */

//! parse instruction parameter list
stmt_on_goto_t::label_list_t parser_t::parse_label_list(
   prog_ctx_t & ctx,
   nu::token_t token,
   nu::token_list_t & tl)
{
   --tl;
   remove_blank(tl);

   stmt_on_goto_t::label_list_t lbl_list;

   while (!tl.empty() &&
          (tl.begin()->type() != tkncl_t::OPERATOR &&
           tl.begin()->identifier() != PROGLANG_STMNT_SEP))
   {
      token = *tl.begin();

      syntax_error_if(token.type() != tkncl_t::IDENTIFIER,
                      token.expression(), token.position());

      std::string label_name = token.identifier();

      lbl_list.push_back(label_name);

      --tl;
      remove_blank(tl);

      if (tl.empty())
         break;

      token = *tl.begin();

      syntax_error_if(
         (token.type() != tkncl_t::OPERATOR &&
          token.type() != tkncl_t::SUBEXP_END)
         ||
         (token.identifier() != PROGLANG_PARAM_SEP &&
          token.identifier() != PROGLANG_STMNT_SEP),
         token.expression(), token.position());

      if (token.identifier() == PROGLANG_STMNT_SEP)
         break;

      --tl;
      remove_blank(tl);
   }

   return lbl_list;
}


/* -------------------------------------------------------------------------- */

// while (expression) {
stmt_t::handle_t parser_t::parse_while(
   prog_ctx_t & ctx,
   token_t token,
   nu::token_list_t & tl)
{
   --tl;
   remove_blank(tl);
   syntax_error_if(tl.empty(), token.expression(), token.position());

   token = *tl.begin();
   --tl;
   remove_blank(tl);

   syntax_error_if(
      token.identifier() != PROGLANG_BEGIN_SUBEXPR_OP ||
      token.type() != tkncl_t::SUBEXP_BEGIN ||
      tl.empty(), token.expression(), token.position());

   token = *tl.rbegin();
   tl--;
   remove_blank(tl);

   syntax_error_if(
      token.identifier() != PROGLANG_BEGIN_BLOCK_OP ||
      token.type() != tkncl_t::IDENTIFIER ||
      tl.empty(), token.expression(), token.position());


   token = *tl.rbegin();
   tl--;
   remove_blank(tl);

   syntax_error_if(
      token.identifier() != PROGLANG_END_SUBEXPR_OP ||
      token.type() != tkncl_t::SUBEXP_END ||
      tl.empty(), token.expression(), token.position());

   expr_parser_t ep;
   remove_blank(tl);

   expr_any_t::handle_t condition = ep.compile(tl, token.position() + 1);
   tl.clear();

   //while (<condition>) {
   return stmt_t::handle_t(
             std::make_shared<stmt_while_t>(ctx, condition));
}


/* -------------------------------------------------------------------------- */

// class identifier {
stmt_t::handle_t parser_t::parse_class(
   prog_ctx_t & ctx,
   token_t token,
   nu::token_list_t & tl)
{

   --tl;
   remove_blank(tl);

   syntax_error_if(
      tl.empty() ||
      token.type() != tkncl_t::IDENTIFIER, token.expression(), token.position());

   token = *tl.begin();
   std::string class_name = token.identifier();

   --tl;
   remove_blank(tl);

   syntax_error_if(
      tl.empty(), token.expression(), token.position());

   token = *tl.begin();

   syntax_error_if(
      tl.size() != 1 ||
      token.identifier() != PROGLANG_BEGIN_BLOCK_OP ||
      token.type() != tkncl_t::IDENTIFIER,
      token.expression(), token.position());

   tl.clear();

   return stmt_t::handle_t(
             std::make_shared<stmt_class_t>(ctx, class_name));
}


/* -------------------------------------------------------------------------- */

// for (init; condition; increment) {
stmt_t::handle_t parser_t::parse_for(
   prog_ctx_t & ctx,
   token_t token,
   nu::token_list_t & tl)
{
   --tl;
   remove_blank(tl);
   syntax_error_if(tl.empty(), token.expression(), token.position());

   token = *tl.begin();
   --tl;
   remove_blank(tl);

   syntax_error_if(
      token.identifier() != PROGLANG_BEGIN_SUBEXPR_OP ||
      token.type() != tkncl_t::SUBEXP_BEGIN ||
      tl.empty(), token.expression(), token.position());

   token = *tl.rbegin();
   tl--;
   remove_blank(tl);

   syntax_error_if(
      token.identifier() != PROGLANG_BEGIN_BLOCK_OP ||
      token.type() != tkncl_t::IDENTIFIER ||
      tl.empty(), token.expression(), token.position());


   token = *tl.rbegin();
   tl--;
   remove_blank(tl);

   syntax_error_if(
      token.identifier() != PROGLANG_END_SUBEXPR_OP ||
      token.type() != tkncl_t::SUBEXP_END ||
      tl.empty(), token.expression(), token.position());

   remove_blank(tl);

   expr_parser_t ep;
   token_list_t etl;

   syntax_error_if(
      tl.empty(), token.expression(), token.position());

   token = *tl.rbegin();

   stmt_t::handle_t init;
   expr_any_t::handle_t condition;
   stmt_t::handle_t increment;

   if (!(token.identifier() == PROGLANG_STMNT_SEP &&
         token.type() == tkncl_t::OPERATOR))
   {
      init = parse_block(ctx, tl, PROGLANG_STMNT_SEP, tkncl_t::OPERATOR);
   }

   else
   {
      --tl;
   }

   remove_blank(tl);

   token = *tl.rbegin();

   if (!(token.identifier() == PROGLANG_STMNT_SEP && token.type() == tkncl_t::OPERATOR))
   {
      move_sub_expression(
         tl,                                      // source
         etl,                                     // destination
         PROGLANG_STMNT_SEP, tkncl_t::OPERATOR    // end-of-expression
      );

      condition = ep.compile(etl, token.position() + 1);
   }

   --tl;
   remove_blank(tl);

   if (!tl.empty())
      increment = parse_block(ctx, tl);

   //for (<init>; <condition>; <increment>) {
   return stmt_t::handle_t(
             std::make_shared<stmt_for_t>(ctx, init, condition, increment));
}


/* -------------------------------------------------------------------------- */

stmt_t::handle_t parser_t::parse_loop_while(
   prog_ctx_t & ctx,
   token_t token,
   nu::token_list_t & tl)
{
   token = *tl.begin();
   --tl;
   remove_blank(tl);

   syntax_error_if(
      token.identifier() != PROGLANG_STMT_WHILE ||
      token.type() != tkncl_t::IDENTIFIER ||
      tl.empty(), token.expression(), token.position());

   token = *tl.begin();
   --tl;
   remove_blank(tl);

   syntax_error_if(
      token.identifier() != PROGLANG_BEGIN_SUBEXPR_OP ||
      token.type() != tkncl_t::SUBEXP_BEGIN ||
      tl.empty(), token.expression(), token.position());

   token = *tl.rbegin();
   tl--;
   remove_blank(tl);

   syntax_error_if(
      token.identifier() != PROGLANG_END_SUBEXPR_OP ||
      token.type() != tkncl_t::SUBEXP_END ||
      tl.empty(), token.expression(), token.position());


   expr_parser_t ep;
   remove_blank(tl);

   syntax_error_if(
      tl.empty(), token.expression(), token.position());

   expr_any_t::handle_t condition = ep.compile(tl, token.position() + 1);
   tl.clear();

   return stmt_t::handle_t(
             std::make_shared<stmt_loop_while_t>(ctx, condition));
}


/* -------------------------------------------------------------------------- */
// parse 'label_identifier:'
stmt_t::handle_t parser_t::parse_label(
   prog_ctx_t & ctx,
   token_t token,
   nu::token_list_t & tl)
{
   //Parse label
   remove_blank(tl);
   syntax_error_if(tl.empty(), token.expression(), token.position());

   //Check for new label
   if (tl.size() > 1 &&
         (tl.begin() + 1)->type() == tkncl_t::OPERATOR &&
         (tl.begin() + 1)->identifier() == PROGLANG_LBL_SEP)
   {
      label_tbl_t& prog_label = ctx.prog_label;

      const std::string& label = token.identifier();

      if (prog_label.is_defined(label))
      {
         syntax_error_if(prog_label[label] != _parsing_line,
                         "Label '" + token.identifier() +
                         "' alrady defined at " + nu::to_string(_parsing_line));
      }

      else
      {
         prog_label.define(token.identifier(), _parsing_line);
      }

      --tl; // remove tokens '<LABEL>' ':'
      remove_blank(tl);
      --tl;
      remove_blank(tl);

      //If there are no statements in this labelled line
      //return an empty statement
      if (tl.empty())
         return std::make_shared<stmt_empty_t>(ctx);

      else
         // parse the rest of tokens...
         return parse_block(ctx, tl);
   }

   return nullptr;
}


/* -------------------------------------------------------------------------- */

stmt_t::handle_t parser_t::parse_goto_stmt(
   prog_ctx_t & ctx,
   token_t token,
   nu::token_list_t & tl)
{
   --tl;
   remove_blank(tl);
   syntax_error_if(tl.empty(), token.expression(), token.position());

   token = *tl.begin();

   return (parse_branch_instr<stmt_goto_t>(ctx, token, tl));
}



/* -------------------------------------------------------------------------- */

stmt_t::handle_t parser_t::parse_end(
   prog_ctx_t & ctx,
   token_t token,
   nu::token_list_t & tl)
{
   --tl;
   remove_blank(tl);
   syntax_error_if(!tl.empty(), token.expression(), token.position());

   return stmt_t::handle_t(std::make_shared<stmt_end_t>(ctx));
}


/* -------------------------------------------------------------------------- */

stmt_t::handle_t parser_t::parse_end_stmt(
   prog_ctx_t & ctx,
   token_t token,
   nu::token_list_t & tl)
{
   --tl;
   remove_blank(tl);

   syntax_error_if(
      ctx.parsing_scope.empty(),
      token.expression(),
      token.position());

   const std::string id = ctx.parsing_scope.top();

   syntax_error_if(
      !tl.empty() &&
      id != PROGLANG_STMT_DO &&
      id != PROGLANG_STMT_IF &&
      id != PROGLANG_STMT_ELIF,
      token.expression(),
      token.position());

   if (id == PROGLANG_STMT_WHILE)
   {
      return stmt_t::handle_t(std::make_shared<stmt_end_while_t>(ctx));
   }

   else if (id == PROGLANG_STMT_IF ||
            id == PROGLANG_STMT_ELSE ||
            id == PROGLANG_STMT_ELIF)
   {
      if (!tl.empty())
      {
         token = *tl.begin();

         bool elif = token.identifier() == PROGLANG_STMT_ELIF;

         syntax_error_if(
            token.type() != tkncl_t::IDENTIFIER ||
            (token.identifier() != PROGLANG_STMT_ELSE && ! elif),
            token.expression(),
            token.position());

         if (elif)
         {
            return parse_if_stmt<stmt_elif_t>(ctx, token, tl);
         }

         else // else-statement
         {
            --tl;
            remove_blank(tl);

            syntax_error_if(tl.empty(), token.expression(), token.position());

            token = *tl.begin();

            syntax_error_if(
               token.type() != tkncl_t::IDENTIFIER ||
               token.identifier() != PROGLANG_BEGIN_BLOCK_OP,
               token.expression(),
               token.position());

            --tl;
            remove_blank(tl);

            syntax_error_if(!tl.empty(), token.expression(), token.position());
            return stmt_t::handle_t(std::make_shared<stmt_else_t>(ctx));
         }

      }

      else
      {
         syntax_error_if(!tl.empty(), token.expression(), token.position());

         return stmt_t::handle_t(std::make_shared<stmt_end_if_t>(ctx));
      }
   }

   else if (id == PROGLANG_STMT_FOR)
   {
      return stmt_t::handle_t(std::make_shared<stmt_end_for_t>(ctx));
   }

   else if (id == PROGLANG_STMT_FUNCTION)
   {
      return stmt_t::handle_t(std::make_shared<stmt_end_func_t>(ctx));
   }

   else if (id == PROGLANG_STMT_DO)
   {
      return parse_loop_while(ctx, token, tl);
   }

   syntax_error(token.expression(), token.position());

   return nullptr;
}


/* -------------------------------------------------------------------------- */

stmt_t::handle_t parser_t::parse_do_stmt(
   prog_ctx_t & ctx,
   token_t token,
   nu::token_list_t & tl)
{
   --tl;
   remove_blank(tl);

   syntax_error_if(tl.empty(),
                   token.expression(),
                   token.position());

   token = *tl.begin();

   syntax_error_if(tl.empty() ||
                   token.type() != tkncl_t::IDENTIFIER ||
                   token.identifier() != "{",
                   token.expression(),
                   token.position());
   --tl;

   return stmt_t::handle_t(std::make_shared<stmt_do_t>(ctx));
}


/* -------------------------------------------------------------------------- */

stmt_t::handle_t parser_t::parse_stmt(
   prog_ctx_t & ctx,
   nu::token_list_t & tl)
{
   remove_blank(tl);

   if (tl.empty())
      return stmt_t::handle_t(std::make_shared<stmt_empty_t>(ctx));

   token_t token(*tl.begin());

   if (token.type() == tkncl_t::NEWLINE)
      return std::make_shared<stmt_empty_t>(ctx);

   const std::string& identifier = token.identifier();

   syntax_error_if(
      (token.type() != tkncl_t::IDENTIFIER) &&
      (token.type() == tkncl_t::OPERATOR &&
       (token.identifier() != PROGLANG_OP_INC &&
        (token.identifier() != PROGLANG_OP_DEC))),
      token.expression(),
      token.position());

   if (identifier == PROGLANG_OP_INC ||
         identifier == PROGLANG_OP_DEC)
      return parse_expr(ctx, token, tl);

   if (identifier == PROGLANG_STMT_COUT)
      return parse_cout(ctx, token, tl);

   if (identifier == PROGLANG_STMT_GOTO)
      return parse_goto_stmt(ctx, token, tl);

   if (identifier == PROGLANG_STMT_END)
      return parse_end(ctx, token, tl);

   if (identifier == PROGLANG_END_BLOCK_OP)
      return parse_end_stmt(ctx, token, tl);

   if (identifier == PROGLANG_STMT_DO)
      return parse_do_stmt(ctx, token, tl);

   if (identifier == PROGLANG_STMT_IF)
      return parse_if_stmt(ctx, token, tl);

   if (identifier == PROGLANG_STMT_FOR)
      return parse_for(ctx, token, tl);

   if (identifier == PROGLANG_STMT_BREAK)
      return parse_break(ctx, token, tl);

   if (identifier == PROGLANG_STMT_ARRAY)
      return parse_parameter_list<stmt_array_t>(
                tkncl_t::SUBSCR_BEGIN,
                tkncl_t::SUBSCR_END,
                ctx,
                token,
                tl,
                PROGLANG_STMNT_SEP,
                ctx);

   if (identifier == PROGLANG_STMT_WHILE)
      return parse_while(ctx, token, tl);

   if (identifier == PROGLANG_STMT_CLASS)
      return parse_class(ctx, token, tl);

   if (identifier == PROGLANG_STMT_FUNCTION)
   {
      return parse_func<stmt_func_t>(ctx, token, tl);
   }

   else
   {
      variable_t::type_t type =
         variable_t::type_by_typename(identifier);

      bool is_constant = false;

      if (identifier == PROGLANG_CONST)
      {
         --tl;
         is_constant = true;

         remove_blank(tl);

         syntax_error_if(tl.empty(),
                         token.expression(),
                         token.position());

         token_t token(*tl.begin());

         syntax_error_if(token.type() != tkncl_t::IDENTIFIER,
                         token.expression(),
                         token.position());

         type = variable_t::type_by_typename(token.identifier());

         syntax_error_if(type == variable_t::type_t::UNDEFINED,
                         token.expression(),
                         token.position());
      }

      else if ( type != variable_t::type_t::UNDEFINED )
      {
         --tl;

         remove_blank(tl);

         syntax_error_if(tl.empty(),
                         token.expression(),
                         token.position());
      }

      else
      {
         auto h = parse_label(ctx, token, tl);

         if (h)
         {
            return h;
         }
      }

      return parse_declaration(ctx, tl, is_constant, type);
   }

   throw exception_t("Syntax error");
}


/* -------------------------------------------------------------------------- */

stmt_t::handle_t parser_t::compile_line(
   nu::token_list_t & tl,
   prog_ctx_t & ctx)
{
   _parsing_line = ctx.compiletime_pc.get_line();
   _parsing_stmt = ctx.compiletime_pc.get_stmt_pos();

   if (ctx.tracing_on)
   {
      std::stringstream log;

      for (auto t : tl)
      {
         log << token_t::description_of(t.type())
             << "[" << t.identifier() << "] ";
      }

      fprintf(ctx.get_stdout_ptr(), "%s\n", log.str().c_str());
   }

   remove_blank(tl);

   if (!tl.empty())
   {
      token_t token(*tl.begin());

      //syntax check
      switch (token.type())
      {
         case tkncl_t::IDENTIFIER:
            break;

         case tkncl_t::OPERATOR:
            syntax_error_if(
               token.identifier() != PROGLANG_OP_DEC &&
               token.identifier() != PROGLANG_OP_INC,
               token.expression(),
               token.position());
            break;

         default:
            syntax_error(token.expression(), token.position());
      }

      return parse_block(ctx, tl);
   }

   return std::make_shared<stmt_empty_t>(ctx);
}


/* -------------------------------------------------------------------------- */

stmt_t::handle_t parser_t::compile_line(
   nu::expr_tknzr_t & st,
   prog_ctx_t & ctx)
{
   nu::token_list_t tl;
   st.get_tknlst(tl);
   return compile_line(tl, ctx);
}


/* -------------------------------------------------------------------------- */

} // namespace nu


/* -------------------------------------------------------------------------- */

